// src/consensus/mod.rs
// 区块链中的共识机制是确保分布式网络中节点对交易和区块状态达成一致的核心算法。
// 交易确认流程
//
// 分叉解决机制: 根据最长链原则，节点会选择累计工作量最大的链作为主链。
// 区块浏览器: 输入交易哈希，查看其所在区块的高度和确认数。
// 钱包软件：自动监控交易状态，显示确认进度。
// 交易回滚概率：在比特币中，6个确认后回滚概率极低（除非发生51%攻击）。
//
// 工作量证明（Proof of Work, PoW）
// 权益证明（Proof of Stake, PoS）

// main.cpp::bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)

use std::sync::Arc;

use thiserror::Error;
use tokio::sync::Mutex;

use bc_base::{
    BlockHeight, BlockId, TransactionId, TransactionValue,
    block::{Block, BlockError},
    transaction::{Transaction, TransactionError},
};
use bc_lib::top_storage::{
    BlockAndIndexStorageWithCache, MiscItemStorageWithCache, TransactionAndIndexStorageWithCache,
};
use bc_util::time_provider::SystemTimeProvider;

use crate::{
    block::{BlockConsensus, BlockConsensusError},
    time::TimeConsensus,
    transaction::{TransactionConsensus, TransactionConsensusError},
};

mod block;
mod time;
mod transaction;

type TimerConsensus = TimeConsensus<SystemTimeProvider>;

#[derive(Error, Debug, PartialEq)]
pub enum ConsensusError {
    #[error("Invalid transaction: {0}")]
    InvalidTransaction(#[from] TransactionError),

    #[error("Invalid block: {0}")]
    InvalidBlock(#[from] BlockError),

    #[error("Consensus transaction error: {0}")]
    ConsensusTxError(#[from] TransactionConsensusError),

    #[error("Consensus block error: {0}")]
    ConsensusBlockError(#[from] BlockConsensusError),
}

#[derive(Debug)]
pub enum LocalTransaction {
    TransactionInMemPool(Transaction),
    TransactionInDb(Transaction),
}

#[derive(Debug)]
pub struct Consensus {
    tx_consensus: Arc<Mutex<TransactionConsensus>>,
    block_consensus: Arc<Mutex<BlockConsensus>>,
    timer_consensus: Arc<Mutex<TimerConsensus>>,
}

impl Consensus {
    /// Create a new Consensus instance with a given difficulty level
    pub async fn new(
        block_and_index_storage_with_cache: Arc<Mutex<BlockAndIndexStorageWithCache>>,
        tx_and_index_storage_with_cache: Arc<Mutex<TransactionAndIndexStorageWithCache>>,
        misc_item_storage_with_cache: Arc<Mutex<MiscItemStorageWithCache>>,
    ) -> Self {
        let tx_consensus = Arc::new(Mutex::new(
            TransactionConsensus::new(
                block_and_index_storage_with_cache.clone(),
                tx_and_index_storage_with_cache.clone(),
                misc_item_storage_with_cache.clone(),
            )
            .await,
        ));
        let block_consensus = Arc::new(Mutex::new(
            BlockConsensus::new(
                block_and_index_storage_with_cache,
                tx_and_index_storage_with_cache,
                misc_item_storage_with_cache,
                tx_consensus.clone(),
            )
            .await,
        ));
        let timer_consensus = Arc::new(Mutex::new(TimeConsensus::new(SystemTimeProvider {})));

        Self {
            tx_consensus,
            block_consensus,
            timer_consensus,
        }
    }

    pub async fn is_coinbase_mature(
        &self,
        tx_id: &TransactionId,
    ) -> Result<bool, TransactionConsensusError> {
        self.tx_consensus
            .lock()
            .await
            .is_coinbase_mature(tx_id)
            .await
    }

    pub async fn get_adjusted_timestamp(&self) -> u64 {
        self.timer_consensus.lock().await.get_adjusted_timestamp()
    }

    pub async fn read_block_height_of_tx(
        &self,
        tx_id: &TransactionId,
    ) -> Result<BlockHeight, ConsensusError> {
        Ok(self
            .tx_consensus
            .lock()
            .await
            .read_block_height_of_tx(tx_id)
            .await?)
    }

    pub async fn read_block_and_txs(&self, block_id: &BlockId) -> Result<Block, ConsensusError> {
        Ok(self
            .block_consensus
            .lock()
            .await
            .read_block_and_txs(block_id)
            .await?)
    }
    pub async fn dump_all_block(&self) {
        self.block_consensus.lock().await.dump_all_block().await;
    }

    pub async fn dump_all_txs(&self) {
        self.tx_consensus.lock().await.dump_all_txs().await;
    }

    pub async fn get_subsidy_for_mining(&self) -> TransactionValue {
        self.block_consensus
            .lock()
            .await
            .get_subsidy_for_mining()
            .await
    }

    pub async fn tx_exists_in_db(&self, tx_id: &TransactionId) -> bool {
        self.tx_consensus.lock().await.tx_exists_in_db(tx_id).await
    }
}

impl Consensus {
    pub async fn process_transaction(
        &mut self,
        tx_id: TransactionId,
        tx: Transaction,
        tx_serialize: Vec<u8>,
    ) -> Result<(), ConsensusError> {
        self.tx_consensus
            .lock()
            .await
            .process_transaction(tx_id, tx, tx_serialize)
            .await?;
        Ok(())
    }

    pub async fn get_transaction(
        &self,
        tx_id: &TransactionId,
    ) -> Result<LocalTransaction, ConsensusError> {
        let r = self
            .tx_consensus
            .lock()
            .await
            .get_transaction(tx_id)
            .await?;
        Ok(r)
    }

    pub async fn process_block(&mut self, block: &Block) -> Result<Vec<Block>, ConsensusError> {
        let r = self
            .block_consensus
            .lock()
            .await
            .process_block(block)
            .await?;

        let mut tx_consensus = self.tx_consensus.lock().await;
        for block in r.iter() {
            tx_consensus.remove_transactions_from_tx_pool(block).await;
        }

        Ok(r)
    }

    pub async fn get_next_work_required(&self, prev_block_id: &BlockId) -> u32 {
        self.block_consensus
            .lock()
            .await
            .get_next_work_required(prev_block_id)
            .await
    }

    pub async fn read_latest_block_id_in_best_chain(&self) -> BlockId {
        self.block_consensus
            .lock()
            .await
            .read_latest_block_id_in_best_chain()
            .await
    }

    pub async fn read_best_height(&self) -> BlockHeight {
        self.block_consensus.lock().await.read_best_height().await
    }
}

#[cfg(test)]
mod tests_for_learning {
    #[test]
    fn test_1() {
        let mut a: i64 = 100;
        let b = 20;

        a >>= b / 10;
        assert!(a == 25);
    }
}
